package com.lagou.ioc;

import com.lagou.annotation.Autowired;
import com.lagou.annotation.Component;
import com.lagou.annotation.Service;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * @author 杨鹏
 *
 * 注解式工厂类，生产对象（使用反射技术）
 */
public class AnnotationBeanFactory {

    // 存储对象
    private static Map<String,Object> map = new HashMap<>();
    private static int ABSOLUTE_PATH_LEN = 0;
    private static Set<Class<?>> classSet = new HashSet<>();
    private static Map<Class<?>, String> classNameMap = new HashMap<>();

    private static List<Class<?>> DEPENDENCY_MAP = new ArrayList<>();
    static {
        /**
         * @Service 作为通用主键   针对实现类和及接口进行注册，beanId可以实现自定义命名（TransferServiceImpl）
         * @Autowired 注入，共实现三种注入 构造器注入（TransactionManager中ConnectionUtil的注入）
         * 方法注入（TransferServiceImpl的accountDao的注入） 属性注入（ProxyFactory的TransactionManager注入）
         * @Transactional 作为事物管理器，对已经生成的组件生成代理对象
         */
        //readAnnotation();
    }

    /**
     * 初始化所有注解主键
     */
    public static void readAnnotation(String packageName){
        try{
            //扫描类文件
            classSet = scanClass(packageName);
            //组件列表
            Set<Class<?>> serviceList = new HashSet<>();
            //需要进行动态代理的组件
            for (Class<?> aClass : classSet) {
                //剔除注解类
                if(aClass.isAnnotation()){
                    continue;
                }
                if(aClass.isAnnotationPresent(Service.class) || aClass.isAnnotationPresent(Component.class)){
                    serviceList.add(aClass);
                }
            }
            //生成Service注解下的bean
            for (Class<?> aClass : serviceList) {
                String beanName = getBeanName(aClass);
                Class<?>[] interfaces = aClass.getInterfaces();
                for (Class<?> anInterface : interfaces) {
                    classNameMap.put(anInterface,beanName);
                }
                classNameMap.put(aClass,beanName);
            }
            //扫描@Autowired
            // 循环依赖检测存储
            Map<Class<?>, Constructor> constructorMap = new HashMap<>(16);
            //set方法注入
            Map<Class<?>, List<Method>> methodMap = new HashMap<>(16);
            //属性注入
            Map<Class<?>, List<Field>> fieldMap = new HashMap<>(16);

            for (Class<?> aClass : classSet) {
                doGetAutowiredInfo(constructorMap, methodMap, fieldMap, aClass);
            }
            //先初始化被注入的对象，记录依赖关系,避免出现循环构造依赖
            for (Class<?> aClass : constructorMap.keySet()) {
                //如果作为依赖的依赖而被提前初始化了，直接返回
                if(map.containsKey(classNameMap.get(aClass))){
                    continue;
                }
                Constructor constructor = constructorMap.get(aClass);
                doInitConstructDependency(constructorMap,serviceList,aClass, constructor);
            }
            //初始化剩余service
            for (Class<?> aClass : serviceList) {
                String beanName = classNameMap.get(aClass);
                if(map.containsKey(beanName)){
                    continue;
                }
                map.put(beanName,aClass.newInstance());
            }
            //方法注入
            for (Class<?> aClass : methodMap.keySet()) {
                doAutowiredMethod(aClass,methodMap.get(aClass));
            }
            //属性注入
            for (Class<?> aClass : fieldMap.keySet()) {
                doAutowiredField(aClass,fieldMap.get(aClass));
            }
            //applicationContextAware注入
            map.put(ApplicationContext.class.getName(),new ApplicationContext());
            //扫描实现ApplicationContextAware接口的类，进行注入
            for(Class aclass : serviceList){
                if(ApplicationContextAware.class.isAssignableFrom(aclass)){
                    Method method = aclass.getMethod("setApplicationContext",ApplicationContext.class);
                    method.invoke(getBean(classNameMap.get(aclass)),getBean(ApplicationContext.class.getName()));
                }
            }


        }catch (Exception ex){
            ex.printStackTrace();
        }
    }

    /**
     * 属性注入
     * @param aClass
     * @param fields
     * @throws IllegalAccessException
     */
    private static void doAutowiredField(Class<?> aClass, List<Field> fields) throws IllegalAccessException {
        for (Field field : fields) {
            field.setAccessible(true);
            Class<?> type = field.getType();
            String beanName = classNameMap.get(type);
            String sourceBeanName = classNameMap.get(aClass);
            Object o = map.get(beanName);
            Object bean = map.get(sourceBeanName);
            field.set(bean,o);
        }
    }

    /**
     * 方法注入
     * @param aClass
     * @param methods
     */
    private static void doAutowiredMethod(Class<?> aClass, List<Method> methods) throws InvocationTargetException, IllegalAccessException {
        for (Method method : methods) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            Object[] params = new Object[parameterTypes.length];
            int i = 0;
            for (Class<?> parameterType : parameterTypes) {
                String beanName = classNameMap.get(parameterType);
                Object o = map.get(beanName);
                params[i++] = o;
            }
            String sourceBeanName = classNameMap.get(aClass);
            Object bean = map.get(sourceBeanName);
            method.invoke(bean,params);
        }
    }

    /**
     * 初始化构造依赖
     * @param serviceList
     * @param constructor
     */
    private static void doInitConstructDependency(Map<Class<?>, Constructor> constructorMap,Set<Class<?>> serviceList,Class<?> aclass, Constructor constructor) throws IllegalAccessException, InvocationTargetException, InstantiationException {
        // TODO 循环依赖异常退出
        DEPENDENCY_MAP.add(constructor.getClass());
        Class[] parameterTypes = constructor.getParameterTypes();
        Object[] params = new Object[parameterTypes.length];
        int i = 0;
        for (Class parameterType : parameterTypes) {
            Object object = getAutowiredBean(constructorMap,parameterType,serviceList);
            params[i++] = object;
        }
        Object o = constructor.newInstance(params);
        String beanName = classNameMap.get(aclass);
        map.put(beanName,o);
    }

    /**
     * 获取需要注入的bean
     * @param parameterType
     * @param serviceList
     * @return
     */
    private static Object getAutowiredBean(Map<Class<?>,Constructor> constructorMap,Class parameterType,Set<Class<?>> serviceList) throws IllegalAccessException, InstantiationException, InvocationTargetException {
        if(serviceList.contains(parameterType)){
            String beanName = classNameMap.get(parameterType);
            Object o = map.get(beanName);
            if(o != null){
                //已经初始化完成
                return o;
            }
            //未初始化，先检测该对象是否通过构造实现
            if(constructorMap.containsKey(constructorMap)){
                doInitConstructDependency(constructorMap,serviceList,parameterType,constructorMap.get(parameterType));
                return map.get(beanName);
            }
            //空参构造初始化
            Object beanObject = parameterType.newInstance();
            map.put(beanName,beanObject);
            return beanObject;
        }
        return null;
    }

    /**
     * 将组件，组件的父接口注册为同一个bean对象
     * @param aclass
     * @return
     */
    private static String getBeanName(Class<?> aclass){
        Service service = aclass.getAnnotation(Service.class);
        Component component = aclass.getAnnotation(Component.class);

        String value;
        if(service != null){
            value = service.value();
        }else{
            value = component.value();
        }
        if(value == null || "".equals(value)) {
            String beanPath = aclass.getName();
            String beanName = beanPath.substring(beanPath.lastIndexOf('.')+1);
            //首字母小写
            char c = beanName.charAt(0);
            String temp = c + "";
            if (c >= 'A' && c <= 'Z') {//当为字母时，则转换为小写
                beanName.replaceFirst(temp, temp.toLowerCase());
            }
            value = beanName;
        }
        //父接口注册
        Class<?>[] interfaces = aclass.getInterfaces();
        for (Class<?> anInterface : interfaces) {
            classNameMap.put(anInterface,value);
        }
        return value;
    }

    /**
     *
     * @param constructorMap 构造器注入列表
     * @param methodMap 方法注入列表
     * @param fieldMap 属性注入列表
     * @param aClass 组件
     * @return
     */
    private static boolean doGetAutowiredInfo(Map<Class<?>, Constructor> constructorMap, Map<Class<?>, List<Method>> methodMap, Map<Class<?>, List<Field>> fieldMap, Class<?> aClass) {
        //剔除注解类
        if(aClass.isAnnotation() || !aClass.isAnnotationPresent(Service.class)){
            return true;
        }
        //构造方法只能有一个能使用Autowired，否则不知道用哪个初始化
        Constructor<?>[] constructors = aClass.getConstructors();
        for (Constructor<?> constructor : constructors) {
            if(constructor.isAnnotationPresent(Autowired.class)){
                constructorMap.put(aClass,constructor);
            }
        }
        // 扫描方法上的Autowired
        Method[] methods = aClass.getMethods();
        for (Method method : methods) {
            if(method.isAnnotationPresent(Autowired.class)){
                if(methodMap.containsKey(aClass)){
                    methodMap.get(aClass).add(method);
                }else{
                    methodMap.put(aClass,new ArrayList<Method>(){{add(method);}});
                }
            }
        }
        // 扫描属性上的Autowired
        Field[] field = aClass.getDeclaredFields();
        for (Field f : field) {
            if(f.isAnnotationPresent(Autowired.class)){
                if(fieldMap.containsKey(aClass)){
                    fieldMap.get(aClass).add(f);
                }else{
                    fieldMap.put(aClass,new ArrayList<Field>(){{add(f);}});
                }
            }
        }
        return false;
    }

    /**
     * 扫描包
     * @return
     */
    public static Set<Class<?>> scanClass(String packageName) throws IOException, ClassNotFoundException {
        //包转绝对路径
        String packageDir = packageName.replace('.', '/');
        URL url = Thread.currentThread().getContextClassLoader().getResource(packageDir);
        String filePath = url.getFile();
        File file = new File(filePath);
        // 绝对路径长度
        ABSOLUTE_PATH_LEN = filePath.length() - packageDir.length() - 1;
        Set<Class<?>> classSet = scanFileDir(file);
        //移除空值
        classSet.remove(null);
        return classSet;
    }

    /**
     * 递归扫描类文件
     * @param file
     * @return
     * @throws IOException
     */
    private static Set<Class<?>> scanFileDir(File file) throws IOException, ClassNotFoundException {
        Set<Class<?>> classes = new HashSet<>();
        if(file.isFile()){
            classes.add(getClassObject(file));
            return classes;
        }
        File[] files = file.listFiles();
        for (File f : files) {
            if(file.isFile()){
                classes.add(getClassObject(file));
            }else{
                Set<Class<?>> classSet = scanFileDir(f);
                classes.addAll(classSet);
            }
        }
        return classes;
    }

    /**
     * 根据引用路径生成Class
     * @param file
     * @return
     */
    private static Class<?> getClassObject(File file) {
        String absolutePath = file.getAbsolutePath();
        String sub = absolutePath.substring(ABSOLUTE_PATH_LEN);
        String className = sub.replace(File.separator, ".");
        className = className.substring(0,className.length()-6);
         try{
             //main运行无法读取Tomcat HttpServlet
             if(className.contains("Servlet")){
                 return null;
             }
            Class<?> aClass = Class.forName(className);
            return aClass;
        }catch (Exception ex){
            //略过无法被加载Class
            ex.printStackTrace();
        }
        return null;
    }


    // 任务二：对外提供获取实例对象的接口（根据id获取）
    public static  Object getBean(String id) {
        return map.get(id);
    }

    public static  Object getBean(Class aclass) {
        String beanName = classNameMap.get(aclass);
        return map.get(beanName);
    }


    public static void initBean(String packageName) {
        readAnnotation(packageName);
    }
}
